//! Event and callback filtering module

#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
use alloc::{
    boxed::Box,
    collections::{btree_map::BTreeMap, btree_set::BTreeSet},
    vec::Vec,
};

use azul_css::{
    props::{
        basic::{LayoutPoint, LayoutRect, LayoutSize},
        property::CssProperty,
    },
    AzString, LayoutDebugMessage,
};
use rust_fontconfig::FcFontCache;

use crate::{
    callbacks::Update,
    dom::{DomId, DomNodeId, On},
    geom::{LogicalPosition, LogicalRect},
    gl::OptionGlContextPtr,
    gpu::GpuEventChanges,
    hit_test::{FullHitTest, HitTestItem, ScrollPosition},
    id::NodeId,
    resources::{ImageCache, RendererResources},
    styled_dom::{ChangedCssProperty, NodeHierarchyItemId},
    task::Instant,
    window::RawWindowHandle,
    FastBTreeSet, FastHashMap,
};

/// Easing functions für smooth scrolling (für Scroll-Animationen)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EasingFunction {
    Linear,
    EaseInOut,
    EaseOut,
}

pub type RestyleNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
pub type RelayoutNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
pub type RelayoutWords = BTreeMap<NodeId, AzString>;

#[derive(Debug, Clone, PartialEq)]
pub struct FocusChange {
    pub old: Option<DomNodeId>,
    pub new: Option<DomNodeId>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct CallbackToCall {
    pub node_id: NodeId,
    pub hit_test_item: Option<HitTestItem>,
    pub event_filter: EventFilter,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ProcessEventResult {
    DoNothing = 0,
    ShouldReRenderCurrentWindow = 1,
    ShouldUpdateDisplayListCurrentWindow = 2,
    // GPU transforms changed: do another hit-test and recurse
    // until nothing has changed anymore
    UpdateHitTesterAndProcessAgain = 3,
    // Only refresh the display (in case of pure scroll or GPU-only events)
    ShouldRegenerateDomCurrentWindow = 4,
    ShouldRegenerateDomAllWindows = 5,
}

impl ProcessEventResult {
    pub fn order(&self) -> usize {
        use self::ProcessEventResult::*;
        match self {
            DoNothing => 0,
            ShouldReRenderCurrentWindow => 1,
            ShouldUpdateDisplayListCurrentWindow => 2,
            UpdateHitTesterAndProcessAgain => 3,
            ShouldRegenerateDomCurrentWindow => 4,
            ShouldRegenerateDomAllWindows => 5,
        }
    }
}

impl PartialOrd for ProcessEventResult {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        self.order().partial_cmp(&other.order())
    }
}

impl Ord for ProcessEventResult {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.order().cmp(&other.order())
    }
}

impl ProcessEventResult {
    pub fn max_self(self, other: Self) -> Self {
        self.max(other)
    }
}

// Phase 3.5: New Event System Types

/// Tracks the origin of an event for proper handling.
///
/// This allows the system to distinguish between user input, programmatic
/// changes, and synthetic events generated by UI components.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum EventSource {
    /// Direct user input (mouse, keyboard, touch, gamepad)
    User,
    /// API call (programmatic scroll, focus change, etc.)
    Programmatic,
    /// Generated from UI interaction (scrollbar drag, synthetic events)
    Synthetic,
    /// Generated from lifecycle hooks (mount, unmount, resize)
    Lifecycle,
}

/// Event propagation phase (similar to DOM Level 2 Events).
///
/// Events can be intercepted at different phases:
/// - **Capture**: Event travels from root down to target (rarely used)
/// - **Target**: Event is at the target element
/// - **Bubble**: Event travels from target back up to root (most common)
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum EventPhase {
    /// Event travels from root down to target
    Capture,
    /// Event is at the target element
    Target,
    /// Event bubbles from target back up to root
    Bubble,
}

impl Default for EventPhase {
    fn default() -> Self {
        EventPhase::Bubble
    }
}

/// Mouse button identifier for mouse events.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum MouseButton {
    Left,
    Middle,
    Right,
    Other(u8),
}

/// Scroll delta mode (how scroll deltas should be interpreted).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum ScrollDeltaMode {
    /// Delta is in pixels
    Pixel,
    /// Delta is in lines (e.g., 3 lines of text)
    Line,
    /// Delta is in pages
    Page,
}

/// Scroll direction for conditional event filtering.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum ScrollDirection {
    Up,
    Down,
    Left,
    Right,
}

/// Reason why a lifecycle event was triggered.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum LifecycleReason {
    /// First appearance in DOM
    InitialMount,
    /// Removed and re-added to DOM
    Remount,
    /// Layout bounds changed
    Resize,
    /// Props or state changed
    Update,
}

/// Keyboard modifier keys state.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
#[repr(C)]
pub struct KeyModifiers {
    pub shift: bool,
    pub ctrl: bool,
    pub alt: bool,
    pub meta: bool,
}

impl KeyModifiers {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_shift(mut self) -> Self {
        self.shift = true;
        self
    }

    pub fn with_ctrl(mut self) -> Self {
        self.ctrl = true;
        self
    }

    pub fn with_alt(mut self) -> Self {
        self.alt = true;
        self
    }

    pub fn with_meta(mut self) -> Self {
        self.meta = true;
        self
    }

    pub fn is_empty(&self) -> bool {
        !self.shift && !self.ctrl && !self.alt && !self.meta
    }
}

/// Type-specific event data for mouse events.
#[derive(Debug, Clone, PartialEq)]
pub struct MouseEventData {
    /// Position of the mouse cursor
    pub position: LogicalPosition,
    /// Which button was pressed/released
    pub button: MouseButton,
    /// Bitmask of currently pressed buttons
    pub buttons: u8,
    /// Modifier keys state
    pub modifiers: KeyModifiers,
}

/// Type-specific event data for keyboard events.
#[derive(Debug, Clone, PartialEq)]
pub struct KeyboardEventData {
    /// The virtual key code
    pub key_code: u32,
    /// The character produced (if any)
    pub char_code: Option<char>,
    /// Modifier keys state
    pub modifiers: KeyModifiers,
    /// Whether this is a repeat event
    pub repeat: bool,
}

/// Type-specific event data for scroll events.
#[derive(Debug, Clone, PartialEq)]
pub struct ScrollEventData {
    /// Scroll delta (dx, dy)
    pub delta: LogicalPosition,
    /// How the delta should be interpreted
    pub delta_mode: ScrollDeltaMode,
}

/// Type-specific event data for touch events.
#[derive(Debug, Clone, PartialEq)]
pub struct TouchEventData {
    /// Touch identifier
    pub id: u64,
    /// Touch position
    pub position: LogicalPosition,
    /// Touch force/pressure (0.0 - 1.0)
    pub force: f32,
}

/// Type-specific event data for clipboard events.
#[derive(Debug, Clone, PartialEq)]
pub struct ClipboardEventData {
    /// The clipboard content (for paste events)
    pub content: Option<String>,
}

/// Type-specific event data for lifecycle events.
#[derive(Debug, Clone, PartialEq)]
pub struct LifecycleEventData {
    /// Why this lifecycle event was triggered
    pub reason: LifecycleReason,
    /// Previous layout bounds (for resize events)
    pub previous_bounds: Option<LogicalRect>,
    /// Current layout bounds
    pub current_bounds: LogicalRect,
}

/// Type-specific event data for window events.
#[derive(Debug, Clone, PartialEq)]
pub struct WindowEventData {
    /// Window size (for resize events)
    pub size: Option<LogicalRect>,
    /// Window position (for move events)
    pub position: Option<LogicalPosition>,
}

/// Union of all possible event data types.
#[derive(Debug, Clone, PartialEq)]
pub enum EventData {
    /// Mouse event data
    Mouse(MouseEventData),
    /// Keyboard event data
    Keyboard(KeyboardEventData),
    /// Scroll event data
    Scroll(ScrollEventData),
    /// Touch event data
    Touch(TouchEventData),
    /// Clipboard event data
    Clipboard(ClipboardEventData),
    /// Lifecycle event data
    Lifecycle(LifecycleEventData),
    /// Window event data
    Window(WindowEventData),
    /// No additional data
    None,
}

/// High-level event type classification.
///
/// This enum categorizes all possible events that can occur in the UI.
/// It extends the existing event system with new event types for
/// lifecycle, clipboard, media, and form handling.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum EventType {

    // Mouse Events

    /// Mouse cursor is over the element
    MouseOver,
    /// Mouse cursor entered the element
    MouseEnter,
    /// Mouse cursor left the element
    MouseLeave,
    /// Mouse button pressed
    MouseDown,
    /// Mouse button released
    MouseUp,
    /// Mouse click (down + up on same element)
    Click,
    /// Mouse double-click
    DoubleClick,
    /// Right-click / context menu
    ContextMenu,

    // Keyboard Events

    /// Key pressed down
    KeyDown,
    /// Key released
    KeyUp,
    /// Character input (respects locale/keyboard layout)
    KeyPress,

    // Focus Events

    /// Element received focus
    Focus,
    /// Element lost focus
    Blur,
    /// Focus entered element or its children
    FocusIn,
    /// Focus left element and its children
    FocusOut,

    // Input Events

    /// Input value is being changed (fires on every keystroke)
    Input,
    /// Input value has changed (fires after editing complete)
    Change,
    /// Form submitted
    Submit,
    /// Form reset
    Reset,
    /// Form validation failed
    Invalid,

    // Scroll Events

    /// Element is being scrolled
    Scroll,
    /// Scroll started
    ScrollStart,
    /// Scroll ended
    ScrollEnd,

    // Drag Events

    /// Drag operation started
    DragStart,
    /// Element is being dragged
    Drag,
    /// Drag operation ended
    DragEnd,
    /// Dragged element entered drop target
    DragEnter,
    /// Dragged element is over drop target
    DragOver,
    /// Dragged element left drop target
    DragLeave,
    /// Element was dropped
    Drop,

    // Touch Events

    /// Touch started
    TouchStart,
    /// Touch moved
    TouchMove,
    /// Touch ended
    TouchEnd,
    /// Touch cancelled
    TouchCancel,

    // Gesture Events

    /// Long press detected (touch or mouse held down)
    LongPress,
    /// Swipe gesture to the left
    SwipeLeft,
    /// Swipe gesture to the right
    SwipeRight,
    /// Swipe gesture upward
    SwipeUp,
    /// Swipe gesture downward
    SwipeDown,
    /// Pinch-in gesture (zoom out)
    PinchIn,
    /// Pinch-out gesture (zoom in)
    PinchOut,
    /// Clockwise rotation gesture
    RotateClockwise,
    /// Counter-clockwise rotation gesture
    RotateCounterClockwise,

    // Clipboard Events

    /// Content copied to clipboard
    Copy,
    /// Content cut to clipboard
    Cut,
    /// Content pasted from clipboard
    Paste,

    // Media Events

    /// Media playback started
    Play,
    /// Media playback paused
    Pause,
    /// Media playback ended
    Ended,
    /// Media time updated
    TimeUpdate,
    /// Media volume changed
    VolumeChange,
    /// Media error occurred
    MediaError,

    // Lifecycle Events

    /// Component was mounted to the DOM
    Mount,
    /// Component will be unmounted from the DOM
    Unmount,
    /// Component was updated
    Update,
    /// Component layout bounds changed
    Resize,

    // Window Events

    /// Window resized
    WindowResize,
    /// Window moved
    WindowMove,
    /// Window close requested
    WindowClose,
    /// Window received focus
    WindowFocusIn,
    /// Window lost focus
    WindowFocusOut,
    /// System theme changed
    ThemeChange,

    // File Events

    /// File is being hovered
    FileHover,
    /// File was dropped
    FileDrop,
    /// File hover cancelled
    FileHoverCancel,
}

/// Unified event wrapper (similar to React's SyntheticEvent).
///
/// All events in the system are wrapped in this structure, providing
/// a consistent interface and enabling event propagation control.
#[derive(Debug, Clone, PartialEq)]
pub struct SyntheticEvent {
    /// The type of event
    pub event_type: EventType,

    /// Where the event came from
    pub source: EventSource,

    /// Current propagation phase
    pub phase: EventPhase,

    /// Target node that the event was dispatched to
    pub target: DomNodeId,

    /// Current node in the propagation path
    pub current_target: DomNodeId,

    /// Timestamp when event was created
    pub timestamp: Instant,

    /// Type-specific event data
    pub data: EventData,

    /// Whether propagation has been stopped
    pub stopped: bool,

    /// Whether immediate propagation has been stopped
    pub stopped_immediate: bool,

    /// Whether default action has been prevented
    pub prevented_default: bool,
}

impl SyntheticEvent {
    /// Create a new synthetic event.
    ///
    /// # Parameters
    /// - `timestamp`: Current time from `(system_callbacks.get_system_time_fn.cb)()`
    pub fn new(
        event_type: EventType,
        source: EventSource,
        target: DomNodeId,
        timestamp: Instant,
        data: EventData,
    ) -> Self {
        Self {
            event_type,
            source,
            phase: EventPhase::Target,
            target,
            current_target: target,
            timestamp,
            data,
            stopped: false,
            stopped_immediate: false,
            prevented_default: false,
        }
    }

    /// Stop event propagation after the current phase completes.
    ///
    /// This prevents the event from reaching handlers in subsequent phases
    /// (e.g., stopping during capture prevents bubble phase).
    pub fn stop_propagation(&mut self) {
        self.stopped = true;
    }

    /// Stop event propagation immediately.
    ///
    /// This prevents any further handlers from being called, even on the
    /// current target element.
    pub fn stop_immediate_propagation(&mut self) {
        self.stopped_immediate = true;
        self.stopped = true;
    }

    /// Prevent the default action associated with this event.
    ///
    /// For example, prevents form submission on Enter key, or prevents
    /// text selection on drag.
    pub fn prevent_default(&mut self) {
        self.prevented_default = true;
    }

    /// Check if propagation was stopped.
    pub fn is_propagation_stopped(&self) -> bool {
        self.stopped
    }

    /// Check if immediate propagation was stopped.
    pub fn is_immediate_propagation_stopped(&self) -> bool {
        self.stopped_immediate
    }

    /// Check if default action was prevented.
    pub fn is_default_prevented(&self) -> bool {
        self.prevented_default
    }
}

// Phase 3.5, Step 3: Event Propagation System

/// Result of event propagation through DOM tree.
#[derive(Debug, Clone)]
pub struct PropagationResult {
    /// Callbacks that should be invoked, in order
    pub callbacks_to_invoke: Vec<(NodeId, EventFilter)>,
    /// Whether default action should be prevented
    pub default_prevented: bool,
}

/// Get the path from root to target node in the DOM tree.
///
/// This is used for event propagation - we need to know which nodes
/// are ancestors of the target to implement capture/bubble phases.
///
/// Returns nodes in order from root to target (inclusive).
pub fn get_dom_path(
    node_hierarchy: &crate::id::NodeHierarchy,
    target_node: NodeHierarchyItemId,
) -> Vec<NodeId> {
    let mut path = Vec::new();
    let target_node_id = match target_node.into_crate_internal() {
        Some(id) => id,
        None => return path,
    };

    let hier_ref = node_hierarchy.as_ref();

    // Build path from target to root
    let mut current = Some(target_node_id);
    while let Some(node_id) = current {
        path.push(node_id);
        current = hier_ref.get(node_id).and_then(|node| node.parent);
    }

    // Reverse to get root → target order
    path.reverse();
    path
}

/// Propagate event through DOM tree with capture and bubble phases.
///
/// This implements DOM Level 2 event propagation:
/// 1. **Capture Phase**: Event travels from root down to target
/// 2. **Target Phase**: Event is at the target element
/// 3. **Bubble Phase**: Event travels from target back up to root
///
/// The event can be stopped at any point via `stopPropagation()` or
/// `stopImmediatePropagation()`.
///
/// # Arguments
/// * `event` - The synthetic event to propagate
/// * `node_hierarchy` - The DOM tree structure
/// * `callbacks` - Map of node IDs to their registered event callbacks
///
/// # Returns
/// `PropagationResult` containing callbacks to invoke and default action state
pub fn propagate_event(
    event: &mut SyntheticEvent,
    node_hierarchy: &crate::id::NodeHierarchy,
    callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
) -> PropagationResult {
    let mut result = PropagationResult {
        callbacks_to_invoke: Vec::new(),
        default_prevented: false,
    };

    // Get path from root to target
    let path = get_dom_path(node_hierarchy, event.target.node);
    if path.is_empty() {
        return result;
    }

    // Phase 1: Capture (root → target)
    event.phase = EventPhase::Capture;
    for &node_id in &path[..path.len().saturating_sub(1)] {
        if event.stopped_immediate {
            break;
        }

        event.current_target = DomNodeId {
            dom: event.target.dom,
            node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
        };

        if let Some(node_callbacks) = callbacks.get(&node_id) {
            for filter in node_callbacks {
                // Check if this filter matches the current phase
                if matches_filter_phase(filter, event, EventPhase::Capture) {
                    result.callbacks_to_invoke.push((node_id, *filter));

                    if event.stopped_immediate {
                        break;
                    }
                }
            }
        }

        if event.stopped {
            break;
        }
    }

    // Phase 2: Target
    if !event.stopped && !path.is_empty() {
        event.phase = EventPhase::Target;
        let target_node_id = *path.last().unwrap();
        event.current_target = event.target;

        if let Some(node_callbacks) = callbacks.get(&target_node_id) {
            for filter in node_callbacks {
                if event.stopped_immediate {
                    break;
                }

                // At target phase, fire both capture and bubble listeners
                if matches_filter_phase(filter, event, EventPhase::Target) {
                    result.callbacks_to_invoke.push((target_node_id, *filter));
                }
            }
        }
    }

    // Phase 3: Bubble (target → root)
    if !event.stopped {
        event.phase = EventPhase::Bubble;

        // Iterate in reverse (excluding target, which was already handled)
        for &node_id in path[..path.len().saturating_sub(1)].iter().rev() {
            if event.stopped_immediate {
                break;
            }

            event.current_target = DomNodeId {
                dom: event.target.dom,
                node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
            };

            if let Some(node_callbacks) = callbacks.get(&node_id) {
                for filter in node_callbacks {
                    if matches_filter_phase(filter, event, EventPhase::Bubble) {
                        result.callbacks_to_invoke.push((node_id, *filter));

                        if event.stopped_immediate {
                            break;
                        }
                    }
                }
            }

            if event.stopped {
                break;
            }
        }
    }

    result.default_prevented = event.prevented_default;
    result
}

/// Check if an event filter matches the given event in the current phase.
///
/// This is used during event propagation to determine which callbacks
/// should be invoked at each phase.
fn matches_filter_phase(
    filter: &EventFilter,
    event: &SyntheticEvent,
    current_phase: EventPhase,
) -> bool {
    // For now, we match based on the filter type
    // In the future, this will also check EventPhase and EventConditions

    match filter {
        EventFilter::Hover(hover_filter) => {
            matches_hover_filter(hover_filter, event, current_phase)
        }
        EventFilter::Focus(focus_filter) => {
            matches_focus_filter(focus_filter, event, current_phase)
        }
        EventFilter::Window(window_filter) => {
            matches_window_filter(window_filter, event, current_phase)
        }
        EventFilter::Not(_) => {
            // Not filters are inverted - will be implemented in future
            false
        }
        EventFilter::Component(_) | EventFilter::Application(_) => {
            // Lifecycle and application events - will be implemented in future
            false
        }
    }
}

/// Check if a hover filter matches the event.
fn matches_hover_filter(
    filter: &HoverEventFilter,
    event: &SyntheticEvent,
    _phase: EventPhase,
) -> bool {
    use HoverEventFilter::*;

    match (filter, &event.event_type) {
        (MouseOver, EventType::MouseOver) => true,
        (MouseDown, EventType::MouseDown) => true,
        (LeftMouseDown, EventType::MouseDown) => {
            // Check if it's left button
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Left
            } else {
                false
            }
        }
        (RightMouseDown, EventType::MouseDown) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Right
            } else {
                false
            }
        }
        (MiddleMouseDown, EventType::MouseDown) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Middle
            } else {
                false
            }
        }
        (MouseUp, EventType::MouseUp) => true,
        (LeftMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Left
            } else {
                false
            }
        }
        (RightMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Right
            } else {
                false
            }
        }
        (MiddleMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Middle
            } else {
                false
            }
        }
        (MouseEnter, EventType::MouseEnter) => true,
        (MouseLeave, EventType::MouseLeave) => true,
        (Scroll, EventType::Scroll) => true,
        (ScrollStart, EventType::ScrollStart) => true,
        (ScrollEnd, EventType::ScrollEnd) => true,
        (TextInput, EventType::Input) => true,
        (VirtualKeyDown, EventType::KeyDown) => true,
        (VirtualKeyUp, EventType::KeyUp) => true,
        (HoveredFile, EventType::FileHover) => true,
        (DroppedFile, EventType::FileDrop) => true,
        (HoveredFileCancelled, EventType::FileHoverCancel) => true,
        (TouchStart, EventType::TouchStart) => true,
        (TouchMove, EventType::TouchMove) => true,
        (TouchEnd, EventType::TouchEnd) => true,
        (TouchCancel, EventType::TouchCancel) => true,
        _ => false,
    }
}

/// Check if a focus filter matches the event.
fn matches_focus_filter(
    filter: &FocusEventFilter,
    event: &SyntheticEvent,
    _phase: EventPhase,
) -> bool {
    use FocusEventFilter::*;

    match (filter, &event.event_type) {
        (MouseOver, EventType::MouseOver) => true,
        (MouseDown, EventType::MouseDown) => true,
        (LeftMouseDown, EventType::MouseDown) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Left
            } else {
                false
            }
        }
        (RightMouseDown, EventType::MouseDown) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Right
            } else {
                false
            }
        }
        (MiddleMouseDown, EventType::MouseDown) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Middle
            } else {
                false
            }
        }
        (MouseUp, EventType::MouseUp) => true,
        (LeftMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Left
            } else {
                false
            }
        }
        (RightMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Right
            } else {
                false
            }
        }
        (MiddleMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Middle
            } else {
                false
            }
        }
        (MouseEnter, EventType::MouseEnter) => true,
        (MouseLeave, EventType::MouseLeave) => true,
        (Scroll, EventType::Scroll) => true,
        (ScrollStart, EventType::ScrollStart) => true,
        (ScrollEnd, EventType::ScrollEnd) => true,
        (TextInput, EventType::Input) => true,
        (VirtualKeyDown, EventType::KeyDown) => true,
        (VirtualKeyUp, EventType::KeyUp) => true,
        (FocusReceived, EventType::Focus) => true,
        (FocusLost, EventType::Blur) => true,
        _ => false,
    }
}

/// Check if a window filter matches the event.
fn matches_window_filter(
    filter: &WindowEventFilter,
    event: &SyntheticEvent,
    _phase: EventPhase,
) -> bool {
    use WindowEventFilter::*;

    match (filter, &event.event_type) {
        (MouseOver, EventType::MouseOver) => true,
        (MouseDown, EventType::MouseDown) => true,
        (LeftMouseDown, EventType::MouseDown) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Left
            } else {
                false
            }
        }
        (RightMouseDown, EventType::MouseDown) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Right
            } else {
                false
            }
        }
        (MiddleMouseDown, EventType::MouseDown) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Middle
            } else {
                false
            }
        }
        (MouseUp, EventType::MouseUp) => true,
        (LeftMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Left
            } else {
                false
            }
        }
        (RightMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Right
            } else {
                false
            }
        }
        (MiddleMouseUp, EventType::MouseUp) => {
            if let EventData::Mouse(mouse_data) = &event.data {
                mouse_data.button == MouseButton::Middle
            } else {
                false
            }
        }
        (MouseEnter, EventType::MouseEnter) => true,
        (MouseLeave, EventType::MouseLeave) => true,
        (Scroll, EventType::Scroll) => true,
        (ScrollStart, EventType::ScrollStart) => true,
        (ScrollEnd, EventType::ScrollEnd) => true,
        (TextInput, EventType::Input) => true,
        (VirtualKeyDown, EventType::KeyDown) => true,
        (VirtualKeyUp, EventType::KeyUp) => true,
        (HoveredFile, EventType::FileHover) => true,
        (DroppedFile, EventType::FileDrop) => true,
        (HoveredFileCancelled, EventType::FileHoverCancel) => true,
        (Resized, EventType::WindowResize) => true,
        (Moved, EventType::WindowMove) => true,
        (TouchStart, EventType::TouchStart) => true,
        (TouchMove, EventType::TouchMove) => true,
        (TouchEnd, EventType::TouchEnd) => true,
        (TouchCancel, EventType::TouchCancel) => true,
        (FocusReceived, EventType::Focus) => true,
        (FocusLost, EventType::Blur) => true,
        (CloseRequested, EventType::WindowClose) => true,
        (ThemeChanged, EventType::ThemeChange) => true,
        (WindowFocusReceived, EventType::WindowFocusIn) => true,
        (WindowFocusLost, EventType::WindowFocusOut) => true,
        _ => false,
    }
}

// Phase 3.5, Step 4: Lifecycle Event Detection

/// Detect lifecycle events by comparing old and new DOM state.
///
/// This function analyzes the differences between two DOM trees and their
/// layouts to generate lifecycle events (Mount, Unmount, Resize).
///
/// # Arguments
/// * `old_dom_id` - DomId of the old DOM (for unmount events)
/// * `new_dom_id` - DomId of the new DOM (for mount events)
/// * `old_hierarchy` - Old DOM node hierarchy
/// * `new_hierarchy` - New DOM node hierarchy
/// * `old_layout` - Old layout rectangles (optional)
/// * `new_layout` - New layout rectangles (optional)
/// * `timestamp` - Current time from system_callbacks.get_system_time_fn.cb()
///
/// # Returns
/// Vector of SyntheticEvents for lifecycle changes
pub fn detect_lifecycle_events(
    old_dom_id: DomId,
    new_dom_id: DomId,
    old_hierarchy: Option<&crate::id::NodeHierarchy>,
    new_hierarchy: Option<&crate::id::NodeHierarchy>,
    old_layout: Option<&BTreeMap<NodeId, LogicalRect>>,
    new_layout: Option<&BTreeMap<NodeId, LogicalRect>>,
    timestamp: Instant,
) -> Vec<SyntheticEvent> {
    let mut events = Vec::new();

    // Collect node IDs from both hierarchies
    let old_nodes: BTreeSet<NodeId> = old_hierarchy
        .map(|h| h.as_ref().linear_iter().map(|id| id).collect())
        .unwrap_or_default();

    let new_nodes: BTreeSet<NodeId> = new_hierarchy
        .map(|h| h.as_ref().linear_iter().map(|id| id).collect())
        .unwrap_or_default();

    // 1. Detect newly mounted nodes (in new but not in old)
    if let Some(new_layout) = new_layout {
        for node_id in new_nodes.difference(&old_nodes) {
            let current_bounds = new_layout
                .get(node_id)
                .copied()
                .unwrap_or(LogicalRect::zero());

            events.push(SyntheticEvent {
                event_type: EventType::Mount,
                source: EventSource::Lifecycle,
                phase: EventPhase::Target,
                target: DomNodeId {
                    dom: new_dom_id,
                    node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
                },
                current_target: DomNodeId {
                    dom: new_dom_id,
                    node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
                },
                timestamp: timestamp.clone(),
                data: EventData::Lifecycle(LifecycleEventData {
                    reason: LifecycleReason::InitialMount,
                    previous_bounds: None,
                    current_bounds,
                }),
                stopped: false,
                stopped_immediate: false,
                prevented_default: false,
            });
        }
    }

    // 2. Detect unmounted nodes (in old but not in new)
    if let Some(old_layout) = old_layout {
        for node_id in old_nodes.difference(&new_nodes) {
            let previous_bounds = old_layout
                .get(node_id)
                .copied()
                .unwrap_or(LogicalRect::zero());

            events.push(SyntheticEvent {
                event_type: EventType::Unmount,
                source: EventSource::Lifecycle,
                phase: EventPhase::Target,
                target: DomNodeId {
                    dom: old_dom_id,
                    node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
                },
                current_target: DomNodeId {
                    dom: old_dom_id,
                    node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
                },
                timestamp: timestamp.clone(),
                data: EventData::Lifecycle(LifecycleEventData {
                    reason: LifecycleReason::InitialMount, // Will be cleaned up
                    previous_bounds: Some(previous_bounds),
                    current_bounds: LogicalRect::zero(),
                }),
                stopped: false,
                stopped_immediate: false,
                prevented_default: false,
            });
        }
    }

    // 3. Detect resized nodes (in both, but bounds changed)
    if let (Some(old_layout), Some(new_layout)) = (old_layout, new_layout) {
        for node_id in old_nodes.intersection(&new_nodes) {
            if let (Some(&old_bounds), Some(&new_bounds)) =
                (old_layout.get(node_id), new_layout.get(node_id))
            {
                // Check if size changed (position changes don't trigger resize)
                if old_bounds.size != new_bounds.size {
                    events.push(SyntheticEvent {
                        event_type: EventType::Resize,
                        source: EventSource::Lifecycle,
                        phase: EventPhase::Target,
                        target: DomNodeId {
                            dom: new_dom_id,
                            node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
                        },
                        current_target: DomNodeId {
                            dom: new_dom_id,
                            node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
                        },
                        timestamp: timestamp.clone(),
                        data: EventData::Lifecycle(LifecycleEventData {
                            reason: LifecycleReason::Resize,
                            previous_bounds: Some(old_bounds),
                            current_bounds: new_bounds,
                        }),
                        stopped: false,
                        stopped_immediate: false,
                        prevented_default: false,
                    });
                }
            }
        }
    }

    events
}

// Phase 3.5: Event Filter System

/// Event filter that only fires when an element is hovered over.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum HoverEventFilter {
    MouseOver,
    MouseDown,
    LeftMouseDown,
    RightMouseDown,
    MiddleMouseDown,
    MouseUp,
    LeftMouseUp,
    RightMouseUp,
    MiddleMouseUp,
    MouseEnter,
    MouseLeave,
    Scroll,
    ScrollStart,
    ScrollEnd,
    TextInput,
    VirtualKeyDown,
    VirtualKeyUp,
    HoveredFile,
    DroppedFile,
    HoveredFileCancelled,
    TouchStart,
    TouchMove,
    TouchEnd,
    TouchCancel,
    /// Pen/stylus made contact with surface
    PenDown,
    /// Pen/stylus moved while in contact
    PenMove,
    /// Pen/stylus lifted from surface
    PenUp,
    /// Pen/stylus entered proximity (hovering without contact)
    PenEnter,
    /// Pen/stylus left proximity
    PenLeave,
    /// Drag started (mouse moved beyond threshold while button down)
    DragStart,
    /// Drag in progress (mouse moved during drag)
    Drag,
    /// Drag ended (mouse button released after drag)
    DragEnd,
    /// Double-click detected (two clicks within time/distance threshold)
    DoubleClick,
    /// Long press detected (button held down for extended time)
    LongPress,
    /// Swipe gesture detected (fast directional movement)
    SwipeLeft,
    SwipeRight,
    SwipeUp,
    SwipeDown,
    /// Pinch gesture (two-finger zoom)
    PinchIn,
    PinchOut,
    /// Rotation gesture (two-finger rotate)
    RotateClockwise,
    RotateCounterClockwise,

    // Internal System Events
    //
    // These events are used internally by the framework for text selection
    // and other system-level features. They are NOT exposed to user callbacks
    // via the On enum and should be filtered out before callback dispatch.
    
    /// Internal: Single click detected (for text cursor placement)
    /// This is distinct from MouseDown - it fires after MouseUp on the same element
    #[doc(hidden)]
    SystemTextSingleClick,
    /// Internal: Double click detected (for word selection)
    #[doc(hidden)]
    SystemTextDoubleClick,
    /// Internal: Triple click detected (for paragraph/line selection)
    #[doc(hidden)]
    SystemTextTripleClick,
}

impl HoverEventFilter {
    /// Check if this is an internal system event that should not be exposed to user callbacks
    pub const fn is_system_internal(&self) -> bool {
        matches!(
            self,
            HoverEventFilter::SystemTextSingleClick
                | HoverEventFilter::SystemTextDoubleClick
                | HoverEventFilter::SystemTextTripleClick
        )
    }

    pub fn to_focus_event_filter(&self) -> Option<FocusEventFilter> {
        match self {
            HoverEventFilter::MouseOver => Some(FocusEventFilter::MouseOver),
            HoverEventFilter::MouseDown => Some(FocusEventFilter::MouseDown),
            HoverEventFilter::LeftMouseDown => Some(FocusEventFilter::LeftMouseDown),
            HoverEventFilter::RightMouseDown => Some(FocusEventFilter::RightMouseDown),
            HoverEventFilter::MiddleMouseDown => Some(FocusEventFilter::MiddleMouseDown),
            HoverEventFilter::MouseUp => Some(FocusEventFilter::MouseUp),
            HoverEventFilter::LeftMouseUp => Some(FocusEventFilter::LeftMouseUp),
            HoverEventFilter::RightMouseUp => Some(FocusEventFilter::RightMouseUp),
            HoverEventFilter::MiddleMouseUp => Some(FocusEventFilter::MiddleMouseUp),
            HoverEventFilter::MouseEnter => Some(FocusEventFilter::MouseEnter),
            HoverEventFilter::MouseLeave => Some(FocusEventFilter::MouseLeave),
            HoverEventFilter::Scroll => Some(FocusEventFilter::Scroll),
            HoverEventFilter::ScrollStart => Some(FocusEventFilter::ScrollStart),
            HoverEventFilter::ScrollEnd => Some(FocusEventFilter::ScrollEnd),
            HoverEventFilter::TextInput => Some(FocusEventFilter::TextInput),
            HoverEventFilter::VirtualKeyDown => Some(FocusEventFilter::VirtualKeyDown),
            HoverEventFilter::VirtualKeyUp => Some(FocusEventFilter::VirtualKeyDown),
            HoverEventFilter::HoveredFile => None,
            HoverEventFilter::DroppedFile => None,
            HoverEventFilter::HoveredFileCancelled => None,
            HoverEventFilter::TouchStart => None,
            HoverEventFilter::TouchMove => None,
            HoverEventFilter::TouchEnd => None,
            HoverEventFilter::TouchCancel => None,
            HoverEventFilter::PenDown => Some(FocusEventFilter::PenDown),
            HoverEventFilter::PenMove => Some(FocusEventFilter::PenMove),
            HoverEventFilter::PenUp => Some(FocusEventFilter::PenUp),
            HoverEventFilter::PenEnter => None,
            HoverEventFilter::PenLeave => None,
            HoverEventFilter::DragStart => Some(FocusEventFilter::DragStart),
            HoverEventFilter::Drag => Some(FocusEventFilter::Drag),
            HoverEventFilter::DragEnd => Some(FocusEventFilter::DragEnd),
            HoverEventFilter::DoubleClick => Some(FocusEventFilter::DoubleClick),
            HoverEventFilter::LongPress => Some(FocusEventFilter::LongPress),
            HoverEventFilter::SwipeLeft => Some(FocusEventFilter::SwipeLeft),
            HoverEventFilter::SwipeRight => Some(FocusEventFilter::SwipeRight),
            HoverEventFilter::SwipeUp => Some(FocusEventFilter::SwipeUp),
            HoverEventFilter::SwipeDown => Some(FocusEventFilter::SwipeDown),
            HoverEventFilter::PinchIn => Some(FocusEventFilter::PinchIn),
            HoverEventFilter::PinchOut => Some(FocusEventFilter::PinchOut),
            HoverEventFilter::RotateClockwise => Some(FocusEventFilter::RotateClockwise),
            HoverEventFilter::RotateCounterClockwise => {
                Some(FocusEventFilter::RotateCounterClockwise)
            }
            // System internal events - don't convert to focus events
            HoverEventFilter::SystemTextSingleClick => None,
            HoverEventFilter::SystemTextDoubleClick => None,
            HoverEventFilter::SystemTextTripleClick => None,
        }
    }
}

/// Event filter similar to `HoverEventFilter` that only fires when the element is focused.
///
/// **Important**: In order for this to fire, the item must have a `tabindex` attribute
/// (to indicate that the item is focus-able).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum FocusEventFilter {
    MouseOver,
    MouseDown,
    LeftMouseDown,
    RightMouseDown,
    MiddleMouseDown,
    MouseUp,
    LeftMouseUp,
    RightMouseUp,
    MiddleMouseUp,
    MouseEnter,
    MouseLeave,
    Scroll,
    ScrollStart,
    ScrollEnd,
    TextInput,
    VirtualKeyDown,
    VirtualKeyUp,
    FocusReceived,
    FocusLost,
    /// Pen events on focused element
    PenDown,
    PenMove,
    PenUp,
    /// Drag started on focused element
    DragStart,
    /// Drag in progress on focused element
    Drag,
    /// Drag ended on focused element
    DragEnd,
    /// Double-click on focused element
    DoubleClick,
    /// Long press on focused element
    LongPress,
    /// Swipe gestures on focused element
    SwipeLeft,
    SwipeRight,
    SwipeUp,
    SwipeDown,
    /// Pinch gesture on focused element
    PinchIn,
    PinchOut,
    /// Rotation gesture on focused element
    RotateClockwise,
    RotateCounterClockwise,
}

/// Event filter that fires when any action fires on the entire window
/// (regardless of whether any element is hovered or focused over).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum WindowEventFilter {
    MouseOver,
    MouseDown,
    LeftMouseDown,
    RightMouseDown,
    MiddleMouseDown,
    MouseUp,
    LeftMouseUp,
    RightMouseUp,
    MiddleMouseUp,
    MouseEnter,
    MouseLeave,
    Scroll,
    ScrollStart,
    ScrollEnd,
    TextInput,
    VirtualKeyDown,
    VirtualKeyUp,
    HoveredFile,
    DroppedFile,
    HoveredFileCancelled,
    Resized,
    Moved,
    TouchStart,
    TouchMove,
    TouchEnd,
    TouchCancel,
    FocusReceived,
    FocusLost,
    CloseRequested,
    ThemeChanged,
    WindowFocusReceived,
    WindowFocusLost,
    /// Pen events anywhere in window
    PenDown,
    PenMove,
    PenUp,
    PenEnter,
    PenLeave,
    /// Drag started anywhere in window
    DragStart,
    /// Drag in progress anywhere in window
    Drag,
    /// Drag ended anywhere in window
    DragEnd,
    /// Double-click anywhere in window
    DoubleClick,
    /// Long press anywhere in window
    LongPress,
    /// Swipe gestures anywhere in window
    SwipeLeft,
    SwipeRight,
    SwipeUp,
    SwipeDown,
    /// Pinch gesture anywhere in window
    PinchIn,
    PinchOut,
    /// Rotation gesture anywhere in window
    RotateClockwise,
    RotateCounterClockwise,
}

impl WindowEventFilter {
    pub fn to_hover_event_filter(&self) -> Option<HoverEventFilter> {
        match self {
            WindowEventFilter::MouseOver => Some(HoverEventFilter::MouseOver),
            WindowEventFilter::MouseDown => Some(HoverEventFilter::MouseDown),
            WindowEventFilter::LeftMouseDown => Some(HoverEventFilter::LeftMouseDown),
            WindowEventFilter::RightMouseDown => Some(HoverEventFilter::RightMouseDown),
            WindowEventFilter::MiddleMouseDown => Some(HoverEventFilter::MiddleMouseDown),
            WindowEventFilter::MouseUp => Some(HoverEventFilter::MouseUp),
            WindowEventFilter::LeftMouseUp => Some(HoverEventFilter::LeftMouseUp),
            WindowEventFilter::RightMouseUp => Some(HoverEventFilter::RightMouseUp),
            WindowEventFilter::MiddleMouseUp => Some(HoverEventFilter::MiddleMouseUp),
            WindowEventFilter::Scroll => Some(HoverEventFilter::Scroll),
            WindowEventFilter::ScrollStart => Some(HoverEventFilter::ScrollStart),
            WindowEventFilter::ScrollEnd => Some(HoverEventFilter::ScrollEnd),
            WindowEventFilter::TextInput => Some(HoverEventFilter::TextInput),
            WindowEventFilter::VirtualKeyDown => Some(HoverEventFilter::VirtualKeyDown),
            WindowEventFilter::VirtualKeyUp => Some(HoverEventFilter::VirtualKeyDown),
            WindowEventFilter::HoveredFile => Some(HoverEventFilter::HoveredFile),
            WindowEventFilter::DroppedFile => Some(HoverEventFilter::DroppedFile),
            WindowEventFilter::HoveredFileCancelled => Some(HoverEventFilter::HoveredFileCancelled),
            // MouseEnter and MouseLeave on the **window** - does not mean a mouseenter
            // and a mouseleave on the hovered element
            WindowEventFilter::MouseEnter => None,
            WindowEventFilter::MouseLeave => None,
            WindowEventFilter::Resized => None,
            WindowEventFilter::Moved => None,
            WindowEventFilter::TouchStart => Some(HoverEventFilter::TouchStart),
            WindowEventFilter::TouchMove => Some(HoverEventFilter::TouchMove),
            WindowEventFilter::TouchEnd => Some(HoverEventFilter::TouchEnd),
            WindowEventFilter::TouchCancel => Some(HoverEventFilter::TouchCancel),
            WindowEventFilter::FocusReceived => None,
            WindowEventFilter::FocusLost => None,
            WindowEventFilter::CloseRequested => None,
            WindowEventFilter::ThemeChanged => None,
            WindowEventFilter::WindowFocusReceived => None, // specific to window!
            WindowEventFilter::WindowFocusLost => None,     // specific to window!
            WindowEventFilter::PenDown => Some(HoverEventFilter::PenDown),
            WindowEventFilter::PenMove => Some(HoverEventFilter::PenMove),
            WindowEventFilter::PenUp => Some(HoverEventFilter::PenUp),
            WindowEventFilter::PenEnter => Some(HoverEventFilter::PenEnter),
            WindowEventFilter::PenLeave => Some(HoverEventFilter::PenLeave),
            WindowEventFilter::DragStart => Some(HoverEventFilter::DragStart),
            WindowEventFilter::Drag => Some(HoverEventFilter::Drag),
            WindowEventFilter::DragEnd => Some(HoverEventFilter::DragEnd),
            WindowEventFilter::DoubleClick => Some(HoverEventFilter::DoubleClick),
            WindowEventFilter::LongPress => Some(HoverEventFilter::LongPress),
            WindowEventFilter::SwipeLeft => Some(HoverEventFilter::SwipeLeft),
            WindowEventFilter::SwipeRight => Some(HoverEventFilter::SwipeRight),
            WindowEventFilter::SwipeUp => Some(HoverEventFilter::SwipeUp),
            WindowEventFilter::SwipeDown => Some(HoverEventFilter::SwipeDown),
            WindowEventFilter::PinchIn => Some(HoverEventFilter::PinchIn),
            WindowEventFilter::PinchOut => Some(HoverEventFilter::PinchOut),
            WindowEventFilter::RotateClockwise => Some(HoverEventFilter::RotateClockwise),
            WindowEventFilter::RotateCounterClockwise => {
                Some(HoverEventFilter::RotateCounterClockwise)
            }
        }
    }
}

/// The inverse of an `onclick` event filter, fires when an item is *not* hovered / focused.
/// This is useful for cleanly implementing things like popover dialogs or dropdown boxes that
/// want to close when the user clicks any where *but* the item itself.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
pub enum NotEventFilter {
    Hover(HoverEventFilter),
    Focus(FocusEventFilter),
}

impl NotEventFilter {
    pub fn as_event_filter(&self) -> EventFilter {
        match self {
            NotEventFilter::Hover(e) => EventFilter::Hover(*e),
            NotEventFilter::Focus(e) => EventFilter::Focus(*e),
        }
    }
}

/// Defines events related to the lifecycle of a DOM node itself.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum ComponentEventFilter {
    /// Fired after the component is first mounted into the DOM.
    AfterMount,
    /// Fired just before the component is removed from the DOM.
    BeforeUnmount,
    /// Fired when the node's layout rectangle has been resized.
    NodeResized,
    /// Fired to trigger the default action for an accessibility component.
    DefaultAction,
    /// Fired when the component becomes selected.
    Selected,
}

/// Defines application-level events not tied to a specific window or node.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum ApplicationEventFilter {
    /// Fired when a new hardware device is connected.
    DeviceConnected,
    /// Fired when a hardware device is disconnected.
    DeviceDisconnected,
    // ... TODO: more events
}

/// Sets the target for what events can reach the callbacks specifically.
///
/// This determines the condition under which an event is fired, such as whether
/// the node is hovered, focused, or if the event is window-global.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
pub enum EventFilter {
    /// Calls the attached callback when the mouse is actively over the
    /// given element.
    Hover(HoverEventFilter),
    /// Inverse of `Hover` - calls the attached callback if the mouse is **not**
    /// over the given element. This is particularly useful for popover menus
    /// where you want to close the menu when the user clicks anywhere else but
    /// the menu itself.
    Not(NotEventFilter),
    /// Calls the attached callback when the element is currently focused.
    Focus(FocusEventFilter),
    /// Calls the callback when anything related to the window is happening.
    /// The "hit item" will be the root item of the DOM.
    /// For example, this can be useful for tracking the mouse position
    /// (in relation to the window). In difference to `Desktop`, this only
    /// fires when the window is focused.
    ///
    /// This can also be good for capturing controller input, touch input
    /// (i.e. global gestures that aren't attached to any component, but rather
    /// the "window" itself).
    Window(WindowEventFilter),
    /// API stub: Something happened with the node itself (node resized, created or removed).
    Component(ComponentEventFilter),
    /// Something happened with the application (started, shutdown, device plugged in).
    Application(ApplicationEventFilter),
}

impl EventFilter {
    pub const fn is_focus_callback(&self) -> bool {
        match self {
            EventFilter::Focus(_) => true,
            _ => false,
        }
    }
    pub const fn is_window_callback(&self) -> bool {
        match self {
            EventFilter::Window(_) => true,
            _ => false,
        }
    }
}

/// Creates a function inside an impl <enum type> block that returns a single
/// variant if the enum is that variant.
///
/// ```rust,no_run,ignore
/// # use azul_core::events::get_single_enum_type;
/// enum A {
///     Abc(AbcType),
/// }
///
/// struct AbcType {}
///
/// impl A {
///     // fn as_abc_type(&self) -> Option<AbcType>
///     get_single_enum_type!(as_abc_type, A::Abc(AbcType));
/// }
/// ```
macro_rules! get_single_enum_type {
    ($fn_name:ident, $enum_name:ident:: $variant:ident($return_type:ty)) => {
        pub fn $fn_name(&self) -> Option<$return_type> {
            use self::$enum_name::*;
            match self {
                $variant(e) => Some(*e),
                _ => None,
            }
        }
    };
}

impl EventFilter {
    get_single_enum_type!(as_hover_event_filter, EventFilter::Hover(HoverEventFilter));
    get_single_enum_type!(as_focus_event_filter, EventFilter::Focus(FocusEventFilter));
    get_single_enum_type!(as_not_event_filter, EventFilter::Not(NotEventFilter));
    get_single_enum_type!(
        as_window_event_filter,
        EventFilter::Window(WindowEventFilter)
    );
}

/// Convert from `On` enum to `EventFilter`.
///
/// This determines which specific filter variant is used based on the event type.
/// For example, `On::TextInput` becomes a Focus event filter, while `On::VirtualKeyDown`
/// becomes a Window event filter (since it's global to the window).
impl From<On> for EventFilter {
    fn from(input: On) -> EventFilter {
        use crate::dom::On::*;
        match input {
            MouseOver => EventFilter::Hover(HoverEventFilter::MouseOver),
            MouseDown => EventFilter::Hover(HoverEventFilter::MouseDown),
            LeftMouseDown => EventFilter::Hover(HoverEventFilter::LeftMouseDown),
            MiddleMouseDown => EventFilter::Hover(HoverEventFilter::MiddleMouseDown),
            RightMouseDown => EventFilter::Hover(HoverEventFilter::RightMouseDown),
            MouseUp => EventFilter::Hover(HoverEventFilter::MouseUp),
            LeftMouseUp => EventFilter::Hover(HoverEventFilter::LeftMouseUp),
            MiddleMouseUp => EventFilter::Hover(HoverEventFilter::MiddleMouseUp),
            RightMouseUp => EventFilter::Hover(HoverEventFilter::RightMouseUp),

            MouseEnter => EventFilter::Hover(HoverEventFilter::MouseEnter),
            MouseLeave => EventFilter::Hover(HoverEventFilter::MouseLeave),
            Scroll => EventFilter::Hover(HoverEventFilter::Scroll),
            TextInput => EventFilter::Focus(FocusEventFilter::TextInput), // focus!
            VirtualKeyDown => EventFilter::Window(WindowEventFilter::VirtualKeyDown), // window!
            VirtualKeyUp => EventFilter::Window(WindowEventFilter::VirtualKeyUp), // window!
            HoveredFile => EventFilter::Hover(HoverEventFilter::HoveredFile),
            DroppedFile => EventFilter::Hover(HoverEventFilter::DroppedFile),
            HoveredFileCancelled => EventFilter::Hover(HoverEventFilter::HoveredFileCancelled),
            FocusReceived => EventFilter::Focus(FocusEventFilter::FocusReceived), // focus!
            FocusLost => EventFilter::Focus(FocusEventFilter::FocusLost),         // focus!

            // Accessibility events - treat as hover events (element-specific)
            Default => EventFilter::Hover(HoverEventFilter::MouseUp), // Default action = click
            Collapse => EventFilter::Hover(HoverEventFilter::MouseUp), // Collapse = click
            Expand => EventFilter::Hover(HoverEventFilter::MouseUp),  // Expand = click
            Increment => EventFilter::Hover(HoverEventFilter::MouseUp), // Increment = click
            Decrement => EventFilter::Hover(HoverEventFilter::MouseUp), // Decrement = click
        }
    }
}

// Cross-Platform Event Dispatch System

/// Target for event dispatch - either a specific node or all root nodes
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CallbackTarget {
    /// Specific node that was hit-tested
    Node { dom_id: DomId, node_id: NodeId },
    /// All root nodes (for window-level events)
    RootNodes,
}

/// A callback that should be invoked, with all necessary context
#[derive(Debug, Clone, PartialEq)]
pub struct CallbackToInvoke {
    /// Which node/window to invoke the callback on
    pub target: CallbackTarget,
    /// The event filter that triggered this callback
    pub event_filter: EventFilter,
    /// Hit test item (for node-level events with spatial info)
    pub hit_test_item: Option<HitTestItem>,
}

/// Result of dispatching events - contains all callbacks that should be invoked
#[derive(Debug, Clone, PartialEq)]
pub struct EventDispatchResult {
    /// Callbacks to invoke, in order
    pub callbacks: Vec<CallbackToInvoke>,
    /// Whether any event had stop_propagation set
    pub propagation_stopped: bool,
}

impl EventDispatchResult {
    pub fn empty() -> Self {
        Self {
            callbacks: Vec::new(),
            propagation_stopped: false,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.callbacks.is_empty()
    }
}

/// Process callback results and potentially generate new synthetic events.
///
/// This function handles the recursive nature of event processing:
/// 1. Process immediate callback results (state changes, images, etc.)
/// 2. Check if new synthetic events should be generated
/// 3. Recursively process those events (up to max_depth to prevent infinite loops)
///
/// Returns true if any callbacks resulted in DOM changes requiring re-layout.
pub fn should_recurse_callbacks<T: CallbackResultRef>(
    callback_results: &[T],
    max_depth: usize,
    current_depth: usize,
) -> bool {
    if current_depth >= max_depth {
        return false;
    }

    // Check if any callback result indicates we should continue processing
    for result in callback_results {
        // If stop_propagation is set, stop processing further events
        if result.stop_propagation() {
            return false;
        }

        // Check if DOM was modified (requires re-layout and re-processing)
        if result.should_regenerate_dom() {
            return current_depth + 1 < max_depth;
        }
    }

    false
}

/// Trait to abstract over callback result types.
/// This allows the core event system to work with results without depending on layout layer.
pub trait CallbackResultRef {
    fn stop_propagation(&self) -> bool;
    fn prevent_default(&self) -> bool;
    fn should_regenerate_dom(&self) -> bool;
}

// Unified Event Determination System (Phase 3.5+)

/// Trait for managers to provide their pending events.
///
/// Each manager (TextInputManager, ScrollManager, etc.) implements this to
/// report what events occurred since the last frame. This enables a unified,
/// lazy event determination system.
pub trait EventProvider {
    /// Get all pending events from this manager.
    ///
    /// Events should include:
    /// - `target`: The DomNodeId that was affected
    /// - `event_type`: What happened (Input, Scroll, Focus, etc.)
    /// - `source`: EventSource::User for input, EventSource::Programmatic for API calls
    /// - `data`: Type-specific event data
    ///
    /// After calling this, the manager should mark events as "read" so they
    /// aren't returned again next frame.
    fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent>;
}

/// Deduplicate synthetic events by (target node, event type).
///
/// When multiple sources generate the same event (e.g., scroll from both
/// ScrollManager and GestureManager), we keep only one.
///
/// Strategy:
/// - Group by (target.dom, target.node, event_type)
/// - Keep the event with the latest timestamp
/// - Merge event data if needed (e.g., accumulate scroll deltas)
pub fn deduplicate_synthetic_events(mut events: Vec<SyntheticEvent>) -> Vec<SyntheticEvent> {
    if events.len() <= 1 {
        return events;
    }

    // Sort by (dom, node, event_type) for grouping
    events.sort_by(|a, b| {
        (a.target.dom, a.target.node, a.event_type).cmp(&(
            b.target.dom,
            b.target.node,
            b.event_type,
        ))
    });

    let mut deduplicated = Vec::new();
    let mut current_group: Option<SyntheticEvent> = None;

    for event in events {
        match &mut current_group {
            None => {
                // Start new group
                current_group = Some(event);
            }
            Some(group) => {
                // Check if same (dom, node, event_type)
                if group.target == event.target && group.event_type == event.event_type {
                    // Duplicate! Keep the one with later timestamp
                    if event.timestamp > group.timestamp {
                        // TODO: Merge event data (e.g., accumulate scroll deltas)
                        *group = event;
                    }
                } else {
                    // Different event, save previous group and start new one
                    deduplicated.push(current_group.take().unwrap());
                    current_group = Some(event);
                }
            }
        }
    }

    // Don't forget the last group
    if let Some(group) = current_group {
        deduplicated.push(group);
    }

    deduplicated
}

/// Dispatch synthetic events to determine which callbacks should be invoked.
///
/// This is the new dispatch function that works with SyntheticEvent instead of the old Events
/// struct. It converts SyntheticEvents to EventFilters and routes them to the correct nodes based
/// on the event target.
///
/// ## Arguments
/// * `events` - Vec of SyntheticEvents to dispatch (already deduplicated)
/// * `hit_test` - Optional hit test for routing hover/mouse events
///
/// ## Returns
/// * `EventDispatchResult` - List of callbacks to invoke with their targets
pub fn dispatch_synthetic_events(
    events: &[SyntheticEvent],
    hit_test: Option<&FullHitTest>,
) -> EventDispatchResult {
    let mut result = EventDispatchResult::empty();

    if events.is_empty() {
        return result;
    }

    for event in events {
        // Convert EventType to EventFilter
        let event_filter = match event.event_type {
            // Mouse events
            EventType::MouseOver => Some(EventFilter::Hover(HoverEventFilter::MouseOver)),
            EventType::MouseEnter => Some(EventFilter::Hover(HoverEventFilter::MouseEnter)),
            EventType::MouseLeave => Some(EventFilter::Hover(HoverEventFilter::MouseLeave)),
            EventType::MouseDown => Some(EventFilter::Hover(HoverEventFilter::LeftMouseDown)),
            EventType::MouseUp => Some(EventFilter::Hover(HoverEventFilter::LeftMouseUp)),
            EventType::Click => Some(EventFilter::Hover(HoverEventFilter::LeftMouseDown)), /* Click = MouseDown for now */
            EventType::DoubleClick => Some(EventFilter::Window(WindowEventFilter::DoubleClick)),
            EventType::ContextMenu => Some(EventFilter::Hover(HoverEventFilter::RightMouseDown)),

            // Keyboard events
            EventType::KeyDown => Some(EventFilter::Focus(FocusEventFilter::VirtualKeyDown)),
            EventType::KeyUp => Some(EventFilter::Focus(FocusEventFilter::VirtualKeyUp)),
            EventType::KeyPress => Some(EventFilter::Focus(FocusEventFilter::TextInput)),

            // Focus events
            EventType::Focus => Some(EventFilter::Focus(FocusEventFilter::FocusReceived)),
            EventType::Blur => Some(EventFilter::Focus(FocusEventFilter::FocusLost)),
            EventType::FocusIn => Some(EventFilter::Focus(FocusEventFilter::FocusReceived)),
            EventType::FocusOut => Some(EventFilter::Focus(FocusEventFilter::FocusLost)),

            // Input events
            EventType::Input => Some(EventFilter::Focus(FocusEventFilter::TextInput)),
            EventType::Change => Some(EventFilter::Focus(FocusEventFilter::TextInput)),

            // Scroll events
            EventType::Scroll => Some(EventFilter::Hover(HoverEventFilter::Scroll)),
            EventType::ScrollStart => Some(EventFilter::Hover(HoverEventFilter::Scroll)),
            EventType::ScrollEnd => Some(EventFilter::Hover(HoverEventFilter::Scroll)),

            // Drag events
            EventType::DragStart => Some(EventFilter::Hover(HoverEventFilter::DragStart)),
            EventType::Drag => Some(EventFilter::Hover(HoverEventFilter::Drag)),
            EventType::DragEnd => Some(EventFilter::Hover(HoverEventFilter::DragEnd)),
            EventType::DragEnter => Some(EventFilter::Hover(HoverEventFilter::MouseEnter)),
            EventType::DragOver => Some(EventFilter::Hover(HoverEventFilter::MouseOver)),
            EventType::DragLeave => Some(EventFilter::Hover(HoverEventFilter::MouseLeave)),
            EventType::Drop => Some(EventFilter::Hover(HoverEventFilter::DroppedFile)),

            // Touch events
            EventType::TouchStart => Some(EventFilter::Hover(HoverEventFilter::TouchStart)),
            EventType::TouchMove => Some(EventFilter::Hover(HoverEventFilter::TouchMove)),
            EventType::TouchEnd => Some(EventFilter::Hover(HoverEventFilter::TouchEnd)),
            EventType::TouchCancel => Some(EventFilter::Hover(HoverEventFilter::TouchCancel)),

            // Window events
            EventType::WindowResize => Some(EventFilter::Window(WindowEventFilter::Resized)),
            EventType::WindowMove => Some(EventFilter::Window(WindowEventFilter::Moved)),
            EventType::WindowClose => Some(EventFilter::Window(WindowEventFilter::CloseRequested)),
            EventType::WindowFocusIn => {
                Some(EventFilter::Window(WindowEventFilter::WindowFocusReceived))
            }
            EventType::WindowFocusOut => {
                Some(EventFilter::Window(WindowEventFilter::WindowFocusLost))
            }
            EventType::ThemeChange => Some(EventFilter::Window(WindowEventFilter::ThemeChanged)),

            // File events
            EventType::FileHover => Some(EventFilter::Hover(HoverEventFilter::HoveredFile)),
            EventType::FileDrop => Some(EventFilter::Hover(HoverEventFilter::DroppedFile)),
            EventType::FileHoverCancel => {
                Some(EventFilter::Hover(HoverEventFilter::HoveredFileCancelled))
            }

            // Unsupported events (clipboard, media, lifecycle, etc.) - skip for now
            _ => None,
        };

        if let Some(filter) = event_filter {
            // Determine callback target based on event target
            // Root node (NodeId::ZERO) → RootNodes target
            // Specific node → Node target
            let callback_target = if event.target.node
                == NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO))
            {
                CallbackTarget::RootNodes
            } else {
                if let Some(node_id) = event.target.node.into_crate_internal() {
                    CallbackTarget::Node {
                        dom_id: event.target.dom,
                        node_id,
                    }
                } else {
                    // Invalid node ID, skip
                    continue;
                }
            };

            // Find hit test item for this node (if available)
            let hit_test_item = if let CallbackTarget::Node { dom_id, node_id } = callback_target {
                hit_test.and_then(|ht| {
                    ht.hovered_nodes
                        .get(&dom_id)
                        .and_then(|hit| hit.regular_hit_test_nodes.get(&node_id))
                        .cloned()
                })
            } else {
                None
            };

            result.callbacks.push(CallbackToInvoke {
                target: callback_target,
                event_filter: filter,
                hit_test_item,
            });
        }
    }

    result
}

#[cfg(test)]
mod tests {
    //! Unit tests for the Phase 3.5 event system
    //!
    //! Tests cover:
    //! - Event type creation
    //! - DOM path traversal
    //! - Event propagation (capture/target/bubble)
    //! - Event filter matching
    //! - Lifecycle event detection

    use std::collections::BTreeMap;

    use crate::{
        dom::{DomId, DomNodeId},
        events::*,
        geom::{LogicalPosition, LogicalRect, LogicalSize},
        id::{Node, NodeHierarchy, NodeId},
        styled_dom::NodeHierarchyItemId,
        task::{Instant, SystemTick},
    };

    // Helper: Create a test Instant
    fn test_instant() -> Instant {
        Instant::Tick(SystemTick::new(0))
    }

    // Helper: Create a simple 3-node tree (root -> child1 -> grandchild)
    fn create_test_hierarchy() -> NodeHierarchy {
        let nodes = vec![
            Node {
                parent: None,
                previous_sibling: None,
                next_sibling: None,
                last_child: Some(NodeId::new(1)),
            },
            Node {
                parent: Some(NodeId::new(0)),
                previous_sibling: None,
                next_sibling: None,
                last_child: Some(NodeId::new(2)),
            },
            Node {
                parent: Some(NodeId::new(1)),
                previous_sibling: None,
                next_sibling: None,
                last_child: None,
            },
        ];
        NodeHierarchy::new(nodes)
    }

    #[test]
    fn test_event_source_enum() {
        // Test that EventSource variants can be created
        let _user = EventSource::User;
        let _programmatic = EventSource::Programmatic;
        let _synthetic = EventSource::Synthetic;
        let _lifecycle = EventSource::Lifecycle;
    }

    #[test]
    fn test_event_phase_enum() {
        // Test that EventPhase variants can be created
        let _capture = EventPhase::Capture;
        let _target = EventPhase::Target;
        let _bubble = EventPhase::Bubble;

        // Test default
        assert_eq!(EventPhase::default(), EventPhase::Bubble);
    }

    #[test]
    fn test_synthetic_event_creation() {
        let dom_id = DomId { inner: 1 };
        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
        let target = DomNodeId {
            dom: dom_id,
            node: node_id,
        };

        let event = SyntheticEvent::new(
            EventType::Click,
            EventSource::User,
            target,
            test_instant(),
            EventData::None,
        );

        assert_eq!(event.event_type, EventType::Click);
        assert_eq!(event.source, EventSource::User);
        assert_eq!(event.phase, EventPhase::Target);
        assert_eq!(event.target, target);
        assert_eq!(event.current_target, target);
        assert!(!event.stopped);
        assert!(!event.stopped_immediate);
        assert!(!event.prevented_default);
    }

    #[test]
    fn test_stop_propagation() {
        let dom_id = DomId { inner: 1 };
        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
        let target = DomNodeId {
            dom: dom_id,
            node: node_id,
        };

        let mut event = SyntheticEvent::new(
            EventType::Click,
            EventSource::User,
            target,
            test_instant(),
            EventData::None,
        );

        assert!(!event.is_propagation_stopped());

        event.stop_propagation();

        assert!(event.is_propagation_stopped());
        assert!(!event.is_immediate_propagation_stopped());
    }

    #[test]
    fn test_stop_immediate_propagation() {
        let dom_id = DomId { inner: 1 };
        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
        let target = DomNodeId {
            dom: dom_id,
            node: node_id,
        };

        let mut event = SyntheticEvent::new(
            EventType::Click,
            EventSource::User,
            target,
            test_instant(),
            EventData::None,
        );

        event.stop_immediate_propagation();

        assert!(event.is_propagation_stopped());
        assert!(event.is_immediate_propagation_stopped());
    }

    #[test]
    fn test_prevent_default() {
        let dom_id = DomId { inner: 1 };
        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
        let target = DomNodeId {
            dom: dom_id,
            node: node_id,
        };

        let mut event = SyntheticEvent::new(
            EventType::Click,
            EventSource::User,
            target,
            test_instant(),
            EventData::None,
        );

        assert!(!event.is_default_prevented());

        event.prevent_default();

        assert!(event.is_default_prevented());
    }

    #[test]
    fn test_get_dom_path_single_node() {
        let hierarchy = NodeHierarchy::new(vec![Node {
            parent: None,
            previous_sibling: None,
            next_sibling: None,
            last_child: None,
        }]);

        let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
        let path = get_dom_path(&hierarchy, target);

        assert_eq!(path.len(), 1);
        assert_eq!(path[0], NodeId::new(0));
    }

    #[test]
    fn test_get_dom_path_three_nodes() {
        let hierarchy = create_test_hierarchy();

        // Test path to grandchild (node 2)
        let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2)));
        let path = get_dom_path(&hierarchy, target);

        assert_eq!(path.len(), 3);
        assert_eq!(path[0], NodeId::new(0)); // root
        assert_eq!(path[1], NodeId::new(1)); // child
        assert_eq!(path[2], NodeId::new(2)); // grandchild
    }

    #[test]
    fn test_get_dom_path_middle_node() {
        let hierarchy = create_test_hierarchy();

        // Test path to middle node (node 1)
        let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1)));
        let path = get_dom_path(&hierarchy, target);

        assert_eq!(path.len(), 2);
        assert_eq!(path[0], NodeId::new(0)); // root
        assert_eq!(path[1], NodeId::new(1)); // child
    }

    #[test]
    fn test_propagate_event_empty_callbacks() {
        let hierarchy = create_test_hierarchy();
        let dom_id = DomId { inner: 1 };
        let target_node = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2)));
        let target = DomNodeId {
            dom: dom_id,
            node: target_node,
        };

        let mut event = SyntheticEvent::new(
            EventType::Click,
            EventSource::User,
            target,
            test_instant(),
            EventData::None,
        );

        let callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
        let result = propagate_event(&mut event, &hierarchy, &callbacks);

        // No callbacks, so nothing should be invoked
        assert_eq!(result.callbacks_to_invoke.len(), 0);
        assert!(!result.default_prevented);
    }

    #[test]
    fn test_mouse_event_data_creation() {
        let mouse_data = MouseEventData {
            position: LogicalPosition { x: 100.0, y: 200.0 },
            button: MouseButton::Left,
            buttons: 1,
            modifiers: KeyModifiers::new(),
        };

        assert_eq!(mouse_data.position.x, 100.0);
        assert_eq!(mouse_data.position.y, 200.0);
        assert_eq!(mouse_data.button, MouseButton::Left);
    }

    #[test]
    fn test_key_modifiers() {
        let modifiers = KeyModifiers::new().with_shift().with_ctrl();

        assert!(modifiers.shift);
        assert!(modifiers.ctrl);
        assert!(!modifiers.alt);
        assert!(!modifiers.meta);
        assert!(!modifiers.is_empty());

        let empty = KeyModifiers::new();
        assert!(empty.is_empty());
    }

    #[test]
    fn test_lifecycle_event_mount() {
        let dom_id = DomId { inner: 1 };
        let old_hierarchy = None;
        let new_hierarchy = create_test_hierarchy();
        let old_layout = None;
        let new_layout = {
            let mut map = BTreeMap::new();
            map.insert(
                NodeId::new(0),
                LogicalRect {
                    origin: LogicalPosition { x: 0.0, y: 0.0 },
                    size: LogicalSize {
                        width: 100.0,
                        height: 100.0,
                    },
                },
            );
            map.insert(
                NodeId::new(1),
                LogicalRect {
                    origin: LogicalPosition { x: 10.0, y: 10.0 },
                    size: LogicalSize {
                        width: 80.0,
                        height: 80.0,
                    },
                },
            );
            map.insert(
                NodeId::new(2),
                LogicalRect {
                    origin: LogicalPosition { x: 20.0, y: 20.0 },
                    size: LogicalSize {
                        width: 60.0,
                        height: 60.0,
                    },
                },
            );
            Some(map)
        };

        let events = detect_lifecycle_events(
            dom_id,
            dom_id,
            old_hierarchy,
            Some(&new_hierarchy),
            old_layout.as_ref(),
            new_layout.as_ref(),
            test_instant(),
        );

        // All 3 nodes should have Mount events
        assert_eq!(events.len(), 3);

        for event in &events {
            assert_eq!(event.event_type, EventType::Mount);
            assert_eq!(event.source, EventSource::Lifecycle);

            if let EventData::Lifecycle(data) = &event.data {
                assert_eq!(data.reason, LifecycleReason::InitialMount);
                assert!(data.previous_bounds.is_none());
            } else {
                panic!("Expected Lifecycle event data");
            }
        }
    }

    #[test]
    fn test_lifecycle_event_unmount() {
        let dom_id = DomId { inner: 1 };
        let old_hierarchy = create_test_hierarchy();
        let new_hierarchy = None;
        let old_layout = {
            let mut map = BTreeMap::new();
            map.insert(
                NodeId::new(0),
                LogicalRect {
                    origin: LogicalPosition { x: 0.0, y: 0.0 },
                    size: LogicalSize {
                        width: 100.0,
                        height: 100.0,
                    },
                },
            );
            Some(map)
        };
        let new_layout = None;

        let events = detect_lifecycle_events(
            dom_id,
            dom_id,
            Some(&old_hierarchy),
            new_hierarchy,
            old_layout.as_ref(),
            new_layout,
            test_instant(),
        );

        // All 3 nodes should have Unmount events
        assert_eq!(events.len(), 3);

        for event in &events {
            assert_eq!(event.event_type, EventType::Unmount);
            assert_eq!(event.source, EventSource::Lifecycle);
        }
    }

    #[test]
    fn test_lifecycle_event_resize() {
        let dom_id = DomId { inner: 1 };
        let hierarchy = create_test_hierarchy();

        let old_layout = {
            let mut map = BTreeMap::new();
            map.insert(
                NodeId::new(0),
                LogicalRect {
                    origin: LogicalPosition { x: 0.0, y: 0.0 },
                    size: LogicalSize {
                        width: 100.0,
                        height: 100.0,
                    },
                },
            );
            Some(map)
        };

        let new_layout = {
            let mut map = BTreeMap::new();
            map.insert(
                NodeId::new(0),
                LogicalRect {
                    origin: LogicalPosition { x: 0.0, y: 0.0 },
                    size: LogicalSize {
                        width: 200.0,
                        height: 100.0,
                    }, // Width changed
                },
            );
            Some(map)
        };

        let events = detect_lifecycle_events(
            dom_id,
            dom_id,
            Some(&hierarchy),
            Some(&hierarchy),
            old_layout.as_ref(),
            new_layout.as_ref(),
            test_instant(),
        );

        // Should have 1 Resize event
        assert_eq!(events.len(), 1);
        assert_eq!(events[0].event_type, EventType::Resize);
        assert_eq!(events[0].source, EventSource::Lifecycle);

        if let EventData::Lifecycle(data) = &events[0].data {
            assert_eq!(data.reason, LifecycleReason::Resize);
            assert!(data.previous_bounds.is_some());
            assert_eq!(data.current_bounds.size.width, 200.0);
        } else {
            panic!("Expected Lifecycle event data");
        }
    }

    #[test]
    fn test_event_filter_hover_match() {
        let dom_id = DomId { inner: 1 };
        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
        let target = DomNodeId {
            dom: dom_id,
            node: node_id,
        };

        let _event = SyntheticEvent::new(
            EventType::MouseDown,
            EventSource::User,
            target,
            test_instant(),
            EventData::Mouse(MouseEventData {
                position: LogicalPosition { x: 0.0, y: 0.0 },
                button: MouseButton::Left,
                buttons: 1,
                modifiers: KeyModifiers::new(),
            }),
        );

        // This is tested internally via matches_hover_filter
        // We can't test it directly without making the function public
        // but it's tested indirectly through propagate_event
    }
}

// Internal System Event Processing

/// Internal system event generated from click detection
#[derive(Debug, Clone, PartialEq)]
pub struct InternalSystemEvent {
    /// The type of system event
    pub event_type: HoverEventFilter,
    /// Target node that was clicked
    pub target: DomNodeId,
    /// Position of the click
    pub position: LogicalPosition,
    /// Timestamp of the event
    pub timestamp: Instant,
}

/// Result of pre-callback internal event filtering
#[derive(Debug, Clone, PartialEq)]
pub struct PreCallbackFilterResult {
    /// Internal system events to process BEFORE user callbacks
    pub internal_events: Vec<PreCallbackSystemEvent>,
    /// Regular events that will be passed to user callbacks
    pub user_events: Vec<SyntheticEvent>,
}

/// Mouse button state for drag tracking
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MouseButtonState {
    pub left_down: bool,
    pub right_down: bool,
    pub middle_down: bool,
}

/// System event to process AFTER user callbacks
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PostCallbackSystemEvent {
    /// Apply text input to focused contenteditable element
    ApplyTextInput,
    /// Focus changed during callbacks
    FocusChanged,
    /// Apply text changeset (separate creation from application)
    ApplyTextChangeset,
    /// Scroll cursor/selection into view
    ScrollIntoView,
    /// Start auto-scroll timer for drag-to-scroll
    StartAutoScrollTimer,
    /// Cancel auto-scroll timer
    CancelAutoScrollTimer,
}

/// Internal system event for pre-callback processing
#[derive(Debug, Clone, PartialEq)]
pub enum PreCallbackSystemEvent {
    /// Single/double/triple click for text selection
    TextClick {
        target: DomNodeId,
        position: LogicalPosition,
        click_count: u8,
        timestamp: Instant,
    },
    /// Mouse drag for selection extension
    TextDragSelection {
        target: DomNodeId,
        start_position: LogicalPosition,
        current_position: LogicalPosition,
        is_dragging: bool,
    },
    /// Arrow key navigation with optional selection extension
    ArrowKeyNavigation {
        target: DomNodeId,
        direction: ArrowDirection,
        extend_selection: bool, // Shift key held
        word_jump: bool,        // Ctrl key held
    },
    /// Keyboard shortcut (Ctrl+C/X/A)
    KeyboardShortcut {
        target: DomNodeId,
        shortcut: KeyboardShortcut,
    },
    /// Delete currently selected text (Backspace/Delete key)
    DeleteSelection {
        target: DomNodeId,
        forward: bool, // true = Delete key (forward), false = Backspace (backward)
    },
}

/// Arrow key directions
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArrowDirection {
    Left,
    Right,
    Up,
    Down,
}

/// Keyboard shortcuts for text editing
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyboardShortcut {
    Copy,      // Ctrl+C
    Cut,       // Ctrl+X
    Paste,     // Ctrl+V
    SelectAll, // Ctrl+A
    Undo,      // Ctrl+Z
    Redo,      // Ctrl+Y or Ctrl+Shift+Z
}

/// Result of post-callback internal event filtering  
#[derive(Debug, Clone, PartialEq)]
pub struct PostCallbackFilterResult {
    /// System events to process AFTER user callbacks
    pub system_events: Vec<PostCallbackSystemEvent>,
}

/// Pre-callback filter: Analyze events and extract internal system events.
///
/// This runs BEFORE user callbacks are invoked. It:
/// 1. Analyzes click patterns for text selection (single/double/triple click)
/// 2. Detects mouse drag for selection extension
/// 3. Processes arrow keys with Shift (selection) and Ctrl (word jump)
/// 4. Detects keyboard shortcuts (Ctrl+C/X/V/A/Z)
/// 5. Generates internal system events for framework processing
/// 6. Filters events to separate internal from user-facing
///
/// ## Arguments
/// * `events` - Input synthetic events from state diffing
/// * `hit_test` - Current hit test for finding clicked nodes
/// * `click_count` - Current click count from SelectionManager
/// * `mouse_down` - Whether left mouse button is currently pressed (for drag)
/// * `drag_start_position` - Position where drag started (if dragging)
/// * `focused_node` - Currently focused node (for keyboard events)
///
/// ## Returns
/// * `PreCallbackFilterResult` - Separated internal and user events
/// Pre-callback filter: Extract internal system events from synthetic events.
///
/// This function runs AFTER managers have been updated with current state (hit test,
/// keyboard state, mouse state, etc.) but BEFORE user callbacks. It queries the managers
/// to detect multi-frame event patterns.
///
/// ## Manager State Dependencies
/// - `selection_manager`: Already updated with click state via `update_click_count()`
/// - `focus_manager`: Contains current `focused_node` from focus tracking
/// - `hover_manager`: Tracks previous mouse positions for drag detection
/// - `keyboard_state`: Current keyboard state from window
/// - `mouse_state`: Current mouse button states from window
///
/// ## Arguments
/// * `events` - Synthetic events to filter
/// * `hit_test` - Current hit test results
/// * `keyboard_state` - Window keyboard state (for modifier keys, virtual key codes)
/// * `mouse_state` - Window mouse state (for button down tracking)
/// * `selection_manager` - Selection manager (for click count, drag state)
/// * `focus_manager` - Focus manager (for focused node)
///
/// ## Returns
/// * `PreCallbackFilterResult` - Separated internal and user events
pub fn pre_callback_filter_internal_events<SM, FM>(
    events: &[SyntheticEvent],
    hit_test: Option<&FullHitTest>,
    keyboard_state: &crate::window::KeyboardState,
    mouse_state: &crate::window::MouseState,
    selection_manager: &SM,
    focus_manager: &FM,
) -> PreCallbackFilterResult
where
    SM: SelectionManagerQuery,
    FM: FocusManagerQuery,
{
    let mut internal_events = Vec::new();
    let mut user_events = Vec::new();

    // Query managers for current state
    let click_count = selection_manager.get_click_count();
    let focused_node = focus_manager.get_focused_node_id();
    let drag_start_position = selection_manager.get_drag_start_position();

    for event in events {
        match event.event_type {
            // Mouse Events
            EventType::MouseDown => {
                // Handle click for text selection
                if click_count > 0 && click_count <= 3 {
                    if let Some(ht) = hit_test {
                        if let Some((dom_id, hit_data)) = ht.hovered_nodes.iter().next() {
                            if let Some(node_id) = hit_data.regular_hit_test_nodes.keys().next() {
                                let position = match &event.data {
                                    EventData::Mouse(mouse_data) => mouse_data.position,
                                    _ => LogicalPosition::zero(),
                                };

                                internal_events.push(PreCallbackSystemEvent::TextClick {
                                    target: DomNodeId {
                                        dom: *dom_id,
                                        node: NodeHierarchyItemId::from_crate_internal(Some(
                                            *node_id,
                                        )),
                                    },
                                    position,
                                    click_count,
                                    timestamp: event.timestamp.clone(),
                                });
                            }
                        }
                    }
                }
                user_events.push(event.clone());
            }

            EventType::MouseOver => {
                // Handle drag selection - analyze state instead of tracking events
                // Drag = left mouse button down + mouse moving
                if mouse_state.left_down {
                    if let Some(start_pos) = drag_start_position {
                        if let Some(ht) = hit_test {
                            if let Some((dom_id, hit_data)) = ht.hovered_nodes.iter().next() {
                                if let Some(node_id) = hit_data.regular_hit_test_nodes.keys().next()
                                {
                                    let current_position = match &event.data {
                                        EventData::Mouse(mouse_data) => mouse_data.position,
                                        _ => LogicalPosition::zero(),
                                    };

                                    internal_events.push(
                                        PreCallbackSystemEvent::TextDragSelection {
                                            target: DomNodeId {
                                                dom: *dom_id,
                                                node: NodeHierarchyItemId::from_crate_internal(
                                                    Some(*node_id),
                                                ),
                                            },
                                            start_position: start_pos,
                                            current_position,
                                            is_dragging: true,
                                        },
                                    );
                                }
                            }
                        }
                    }
                }
                user_events.push(event.clone());
            }

            // Keyboard Events
            EventType::KeyDown => {
                if let Some(target) = focused_node {
                    if let EventData::Keyboard(kbd_data) = &event.data {
                        // Use KeyboardState instead of event modifiers for consistency
                        let ctrl_pressed = keyboard_state.ctrl_down();
                        let shift_pressed = keyboard_state.shift_down();

                        // Check for keyboard shortcuts using VirtualKeyCode (more robust than
                        // char_code) This works across all keyboard layouts
                        // and input methods
                        use crate::window::VirtualKeyCode;
                        if ctrl_pressed {
                            let shortcut = match keyboard_state.current_virtual_keycode.as_ref() {
                                Some(VirtualKeyCode::C) => Some(KeyboardShortcut::Copy),
                                Some(VirtualKeyCode::X) => Some(KeyboardShortcut::Cut),
                                Some(VirtualKeyCode::V) => Some(KeyboardShortcut::Paste),
                                Some(VirtualKeyCode::A) => Some(KeyboardShortcut::SelectAll),
                                Some(VirtualKeyCode::Z) if !shift_pressed => {
                                    Some(KeyboardShortcut::Undo)
                                }
                                Some(VirtualKeyCode::Z) if shift_pressed => {
                                    Some(KeyboardShortcut::Redo)
                                }
                                Some(VirtualKeyCode::Y) => Some(KeyboardShortcut::Redo),
                                _ => None,
                            };

                            if let Some(shortcut) = shortcut {
                                internal_events.push(PreCallbackSystemEvent::KeyboardShortcut {
                                    target,
                                    shortcut,
                                });
                                // Don't pass shortcuts to user callbacks
                                continue;
                            }
                        }

                        // Check for arrow key navigation using VirtualKeyCode
                        // Use keyboard_state.current_virtual_keycode for state-based detection
                        let direction =
                            if let Some(vk) = keyboard_state.current_virtual_keycode.as_ref() {
                                match vk {
                                    VirtualKeyCode::Left => Some(ArrowDirection::Left),
                                    VirtualKeyCode::Up => Some(ArrowDirection::Up),
                                    VirtualKeyCode::Right => Some(ArrowDirection::Right),
                                    VirtualKeyCode::Down => Some(ArrowDirection::Down),
                                    _ => None,
                                }
                            } else {
                                None
                            };

                        if let Some(direction) = direction {
                            internal_events.push(PreCallbackSystemEvent::ArrowKeyNavigation {
                                target,
                                direction,
                                extend_selection: shift_pressed,
                                word_jump: ctrl_pressed,
                            });
                            // Don't pass arrow keys to user callbacks when in contenteditable
                            continue;
                        }

                        // Check for Backspace/Delete keys when selection exists
                        if let Some(vk) = keyboard_state.current_virtual_keycode.as_ref() {
                            let should_delete = match vk {
                                VirtualKeyCode::Back => {
                                    // Backspace - delete backward (selection or single char)
                                    if selection_manager.has_selection() {
                                        Some(false) // backward deletion
                                    } else {
                                        None // No selection, pass to user callbacks
                                    }
                                }
                                VirtualKeyCode::Delete => {
                                    // Delete - delete forward (selection or single char)
                                    if selection_manager.has_selection() {
                                        Some(true) // forward deletion
                                    } else {
                                        None // No selection, pass to user callbacks
                                    }
                                }
                                _ => None,
                            };

                            if let Some(forward) = should_delete {
                                internal_events.push(PreCallbackSystemEvent::DeleteSelection {
                                    target,
                                    forward,
                                });
                                // Don't pass to user callbacks - we handle it
                                continue;
                            }
                        }
                    }
                }
                user_events.push(event.clone());
            }

            // All other events pass through
            _ => {
                user_events.push(event.clone());
            }
        }
    }

    PreCallbackFilterResult {
        internal_events,
        user_events,
    }
}

/// Trait for querying selection manager state.
///
/// This allows `pre_callback_filter_internal_events` to query manager state
/// without depending on the concrete `SelectionManager` type from layout crate.
pub trait SelectionManagerQuery {
    /// Get the current click count (1 = single, 2 = double, 3 = triple)
    fn get_click_count(&self) -> u8;

    /// Get the drag start position if a drag is in progress
    fn get_drag_start_position(&self) -> Option<LogicalPosition>;

    /// Check if any selection exists (click selection or drag selection)
    fn has_selection(&self) -> bool;
}

/// Trait for querying focus manager state.
///
/// This allows `pre_callback_filter_internal_events` to query manager state
/// without depending on the concrete `FocusManager` type from layout crate.
pub trait FocusManagerQuery {
    /// Get the currently focused node ID
    fn get_focused_node_id(&self) -> Option<DomNodeId>;
}

/// Post-callback filter: Analyze applied changes and generate system events.
///
/// This runs AFTER user callbacks have been invoked. It analyzes what internal
/// events were processed and determines what system actions are needed (scrolling,
/// timers, etc.).
///
/// ## Architecture
///
/// Pre-callback phase creates internal events (TextClick, ArrowKeyNavigation, etc.).
/// Those events are processed, potentially creating text changes. After user callbacks
/// run, this function analyzes the results to determine scrolling and timer needs.
///
/// ## Arguments
/// * `prevent_default` - Whether any callback prevented default action
/// * `internal_events` - Internal events that were processed (from pre-filter)
/// * `old_focus` - Focus state before callbacks
/// * `new_focus` - Focus state after callbacks
///
/// ## Returns
/// * `PostCallbackFilterResult` - System events to process after callbacks
pub fn post_callback_filter_internal_events(
    prevent_default: bool,
    internal_events: &[PreCallbackSystemEvent],
    old_focus: Option<DomNodeId>,
    new_focus: Option<DomNodeId>,
) -> PostCallbackFilterResult {
    let mut system_events = Vec::new();

    // If callbacks didn't prevent default, apply text input
    if !prevent_default {
        system_events.push(PostCallbackSystemEvent::ApplyTextInput);

        // Analyze internal events to determine scroll needs
        for event in internal_events {
            match event {
                PreCallbackSystemEvent::TextClick { .. } => {
                    // Single/double/triple click changes selection/cursor
                    // Always scroll into view after click
                    system_events.push(PostCallbackSystemEvent::ScrollIntoView);
                }
                PreCallbackSystemEvent::TextDragSelection { is_dragging, .. } => {
                    // Drag selection in progress
                    if *is_dragging {
                        // Start auto-scroll timer if not already running
                        // Timer will check mouse position and scroll continuously
                        system_events.push(PostCallbackSystemEvent::StartAutoScrollTimer);
                    } else {
                        // Drag ended, cancel timer
                        system_events.push(PostCallbackSystemEvent::CancelAutoScrollTimer);
                    }
                }
                PreCallbackSystemEvent::ArrowKeyNavigation { .. } => {
                    // Arrow keys move cursor/selection
                    // Scroll to keep cursor visible
                    system_events.push(PostCallbackSystemEvent::ScrollIntoView);
                }
                PreCallbackSystemEvent::KeyboardShortcut { shortcut, .. } => {
                    use KeyboardShortcut::*;
                    match shortcut {
                        Copy => {
                            // Copy doesn't change selection, no scroll needed
                        }
                        Cut | Paste => {
                            // Cut/paste modifies text and may change cursor position
                            system_events.push(PostCallbackSystemEvent::ScrollIntoView);
                        }
                        SelectAll => {
                            // Select all changes selection but typically no scroll
                            // (user wants to see the whole document selected)
                        }
                        Undo | Redo => {
                            // Undo/redo may restore cursor to different position
                            system_events.push(PostCallbackSystemEvent::ScrollIntoView);
                        }
                    }
                }
                PreCallbackSystemEvent::DeleteSelection { .. } => {
                    // Delete/Backspace removes selection and places cursor
                    // Scroll to keep cursor visible after deletion
                    system_events.push(PostCallbackSystemEvent::ScrollIntoView);
                }
            }
        }
    }

    // If focus changed, mark for processing
    if old_focus != new_focus {
        system_events.push(PostCallbackSystemEvent::FocusChanged);
    }

    PostCallbackFilterResult { system_events }
}

/// Deprecated: Use pre_callback_filter_internal_events instead
#[deprecated(
    since = "0.1.0",
    note = "Use pre_callback_filter_internal_events instead"
)]
pub fn filter_internal_events(events: &[SyntheticEvent]) -> FilteredInternalEvents {
    FilteredInternalEvents {
        internal_events: Vec::new(),
        user_events: events.to_vec(),
    }
}

/// Deprecated: Use pre_callback_filter_internal_events instead
#[deprecated(
    since = "0.1.0",
    note = "Use pre_callback_filter_internal_events instead"
)]
pub fn dispatch_internal_events(
    _events: &FilteredInternalEvents,
    _click_count: u8,
    _current_hit_test: Option<&FullHitTest>,
    _current_position: LogicalPosition,
    _timestamp: Instant,
) -> Vec<InternalSystemEvent> {
    Vec::new()
}

// Deprecated type for compatibility
#[derive(Debug, Clone, PartialEq)]
pub struct FilteredInternalEvents {
    pub internal_events: Vec<InternalSystemEvent>,
    pub user_events: Vec<SyntheticEvent>,
}
